{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ndo4ERqnwQOU"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "MTKwbguKwT4R"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xfNT-mlFwxVM"
      },
      "source": [
        "# Autoencoder 소개"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0TD5ZrvEMbhZ"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/generative/autoencoder\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/tutorials/generative/autoencoder.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/tutorials/generative/autoencoder.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서 소스보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/tutorials/generative/autoencoder.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ITZuApL56Mny"
      },
      "source": [
        "이 튜토리얼에서는 3가지 예(기본 사항, 이미지 노이즈 제거 및 이상 감지)를 통해 autoencoder를 소개합니다.\n",
        "\n",
        "autoencoder는 입력을 출력에 복사하도록 훈련된 특수한 유형의 신경망입니다. 예를 들어, 손으로 쓴 숫자의 이미지가 주어지면 autoencoder는 먼저 이미지를 더 낮은 차원의 잠재 표현으로 인코딩한 다음 잠재 표현을 다시 이미지로 디코딩합니다. autoencoder는 재구성 오류를 최소화하면서 데이터를 압축하는 방법을 학습합니다.\n",
        "\n",
        "autoencoder에 대해 자세히 알아보려면 Ian Goodfellow, Yoshua Bengio 및 Aaron Courville의 [딥 러닝](https://www.deeplearningbook.org/)에서 14장을 읽어보세요."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e1_Y75QXJS6h"
      },
      "source": [
        "## TensorFlow 및 기타 라이브러리 가져오기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YfIk2es3hJEd"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "import tensorflow as tf\n",
        "\n",
        "from sklearn.metrics import accuracy_score, precision_score, recall_score\n",
        "from sklearn.model_selection import train_test_split\n",
        "from tensorflow.keras import layers, losses\n",
        "from tensorflow.keras.datasets import fashion_mnist\n",
        "from tensorflow.keras.models import Model"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iYn4MdZnKCey"
      },
      "source": [
        "## 데이터세트 로드하기\n",
        "\n",
        "시작하려면 Fashon MNIST 데이터세트를 사용하여 기본 autoencoder를 훈련합니다. 이 데이터세트의 각 이미지는 28x28 픽셀입니다. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YZm503-I_tji"
      },
      "outputs": [],
      "source": [
        "(x_train, _), (x_test, _) = fashion_mnist.load_data()\n",
        "\n",
        "x_train = x_train.astype('float32') / 255.\n",
        "x_test = x_test.astype('float32') / 255.\n",
        "\n",
        "print (x_train.shape)\n",
        "print (x_test.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VEdCXSwCoKok"
      },
      "source": [
        "## 첫 번째 예: 기본 autoencoder\n",
        "\n",
        "![기본 오토 인코더 결과](images/intro_autoencoder_result.png)\n",
        "\n",
        "두 개의 Dense 레이어로 autoencoder를 정의합니다. 이미지를 64차원 잠재 벡터로 압축하는 `encoder`와 잠재 공간에서 원본 이미지를 재구성하는 `decoder`입니다.\n",
        "\n",
        "모델을 정의하려면 [Keras Model Subclassing API](https://www.tensorflow.org/guide/keras/custom_layers_and_models)를 사용하세요.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0MUxidpyChjX"
      },
      "outputs": [],
      "source": [
        "latent_dim = 64 \n",
        "\n",
        "class Autoencoder(Model):\n",
        "  def __init__(self, encoding_dim):\n",
        "    super(Autoencoder, self).__init__()\n",
        "    self.latent_dim = latent_dim   \n",
        "    self.encoder = tf.keras.Sequential([\n",
        "      layers.Flatten(),\n",
        "      layers.Dense(latent_dim, activation='relu'),\n",
        "    ])\n",
        "    self.decoder = tf.keras.Sequential([\n",
        "      layers.Dense(784, activation='sigmoid'),\n",
        "      layers.Reshape((28, 28))\n",
        "    ])\n",
        "\n",
        "  def call(self, x):\n",
        "    encoded = self.encoder(x)\n",
        "    decoded = self.decoder(encoded)\n",
        "    return decoded\n",
        "  \n",
        "autoencoder = Autoencoder(latent_dim) "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9I1JlqEIDCI4"
      },
      "outputs": [],
      "source": [
        "autoencoder.compile(optimizer='adam', loss=losses.MeanSquaredError())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7oJSeMTroABs"
      },
      "source": [
        "`x_train`을 입력과 대상으로 사용하여 모델을 훈련합니다. `encoder`는 데이터세트를 784차원에서 잠재 공간으로 압축하는 방법을 배우고, `decoder`는 원본 이미지를 재구성하는 방법을 배웁니다. ."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "h1RI9OfHDBsK"
      },
      "outputs": [],
      "source": [
        "autoencoder.fit(x_train, x_train,\n",
        "                epochs=10,\n",
        "                shuffle=True,\n",
        "                validation_data=(x_test, x_test))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wAM1QBhtoC-n"
      },
      "source": [
        "모델이 훈련되었으므로 테스트 세트에서 이미지를 인코딩 및 디코딩하여 테스트해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Pbr5WCj7FQUi"
      },
      "outputs": [],
      "source": [
        "encoded_imgs = autoencoder.encoder(x_test).numpy()\n",
        "decoded_imgs = autoencoder.decoder(encoded_imgs).numpy()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "s4LlDOS6FUA1"
      },
      "outputs": [],
      "source": [
        "n = 10\n",
        "plt.figure(figsize=(20, 4))\n",
        "for i in range(n):\n",
        "  # display original\n",
        "  ax = plt.subplot(2, n, i + 1)\n",
        "  plt.imshow(x_test[i])\n",
        "  plt.title(\"original\")\n",
        "  plt.gray()\n",
        "  ax.get_xaxis().set_visible(False)\n",
        "  ax.get_yaxis().set_visible(False)\n",
        "\n",
        "  # display reconstruction\n",
        "  ax = plt.subplot(2, n, i + 1 + n)\n",
        "  plt.imshow(decoded_imgs[i])\n",
        "  plt.title(\"reconstructed\")\n",
        "  plt.gray()\n",
        "  ax.get_xaxis().set_visible(False)\n",
        "  ax.get_yaxis().set_visible(False)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "r4gv6G8PoRQE"
      },
      "source": [
        "## 두 번째 예: 이미지 노이즈 제거\n",
        "\n",
        "![이미지 노이즈 제거 결과](images/image_denoise_fmnist_results.png)\n",
        "\n",
        "autoencoder는 이미지에서 노이즈를 제거하도록 훈련될 수도 있습니다. 다음 섹션에서는 각 이미지에 임의의 노이즈를 적용하여 Fashion MNIST 데이터세트의 노이즈 버전을 생성합니다. 그런 다음 노이즈가 있는 이미지를 입력으로 사용하고 원본 이미지를 대상으로 사용하여 autoencoder를 훈련합니다.\n",
        "\n",
        "이전에 수정한 내용을 생략하기 위해 데이터세트를 다시 가져오겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gDYHJA2PCQ3m"
      },
      "outputs": [],
      "source": [
        "(x_train, _), (x_test, _) = fashion_mnist.load_data()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "uJZ-TcaqDBr5"
      },
      "outputs": [],
      "source": [
        "x_train = x_train.astype('float32') / 255.\n",
        "x_test = x_test.astype('float32') / 255.\n",
        "\n",
        "x_train = x_train[..., tf.newaxis]\n",
        "x_test = x_test[..., tf.newaxis]\n",
        "\n",
        "print(x_train.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aPZl_6P65_8R"
      },
      "source": [
        "이미지에 임의의 노이즈를 추가합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "axSMyxC354fc"
      },
      "outputs": [],
      "source": [
        "noise_factor = 0.2\n",
        "x_train_noisy = x_train + noise_factor * tf.random.normal(shape=x_train.shape) \n",
        "x_test_noisy = x_test + noise_factor * tf.random.normal(shape=x_test.shape) \n",
        "\n",
        "x_train_noisy = tf.clip_by_value(x_train_noisy, clip_value_min=0., clip_value_max=1.)\n",
        "x_test_noisy = tf.clip_by_value(x_test_noisy, clip_value_min=0., clip_value_max=1.)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wRxHe4XXltNd"
      },
      "source": [
        "노이즈가 있는 이미지를 플롯합니다.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "thKUmbVVCQpt"
      },
      "outputs": [],
      "source": [
        "n = 10\n",
        "plt.figure(figsize=(20, 2))\n",
        "for i in range(n):\n",
        "    ax = plt.subplot(1, n, i + 1)\n",
        "    plt.title(\"original + noise\")\n",
        "    plt.imshow(tf.squeeze(x_test_noisy[i]))\n",
        "    plt.gray()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Sy9SY8jGl5aP"
      },
      "source": [
        "### 컨볼루셔널 autoencoder 정의하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vT_BhZngWMwp"
      },
      "source": [
        "이 예제에서는 <code>encoder</code>에 <a>Conv2D</a> 레이어를 사용하고 <code>decoder</code>에 <a>Conv2DTranspose</a> 레이어를 사용하여 컨볼루셔널 autoencoder를 훈련합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "R5KjoIlYCQko"
      },
      "outputs": [],
      "source": [
        "class Denoise(Model):\n",
        "  def __init__(self):\n",
        "    super(Denoise, self).__init__()\n",
        "    self.encoder = tf.keras.Sequential([\n",
        "      layers.Input(shape=(28, 28, 1)), \n",
        "      layers.Conv2D(16, (3,3), activation='relu', padding='same', strides=2),\n",
        "      layers.Conv2D(8, (3,3), activation='relu', padding='same', strides=2)])\n",
        "    \n",
        "    self.decoder = tf.keras.Sequential([\n",
        "      layers.Conv2DTranspose(8, kernel_size=3, strides=2, activation='relu', padding='same'),\n",
        "      layers.Conv2DTranspose(16, kernel_size=3, strides=2, activation='relu', padding='same'),\n",
        "      layers.Conv2D(1, kernel_size=(3,3), activation='sigmoid', padding='same')])\n",
        "    \n",
        "  def call(self, x):\n",
        "    encoded = self.encoder(x)\n",
        "    decoded = self.decoder(encoded)\n",
        "    return decoded\n",
        "\n",
        "autoencoder = Denoise()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "QYKbiDFYCQfj"
      },
      "outputs": [],
      "source": [
        "autoencoder.compile(optimizer='adam', loss=losses.MeanSquaredError())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IssFr1BNCQX3"
      },
      "outputs": [],
      "source": [
        "autoencoder.fit(x_train_noisy, x_train,\n",
        "                epochs=10,\n",
        "                shuffle=True,\n",
        "                validation_data=(x_test_noisy, x_test))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "G85xUVBGTAKp"
      },
      "source": [
        "encoder의 요약을 살펴보겠습니다. 이미지가 28x28에서 7x7로 어떻게 다운샘플링되는지 확인하세요."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "oEpxlX6sTEQz"
      },
      "outputs": [],
      "source": [
        "autoencoder.encoder.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DDZBfMx1UtXx"
      },
      "source": [
        "decoder는 이미지를 7x7에서 28x28로 다시 업샘플링합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pbeQtYMaUpro"
      },
      "outputs": [],
      "source": [
        "autoencoder.decoder.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "A7-VAuEy_N6M"
      },
      "source": [
        "autoencoder에서 생성된 노이즈가 있는 이미지와 노이즈가 제거 된 이미지를 모두 플롯합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "t5IyPi1fCQQz"
      },
      "outputs": [],
      "source": [
        "encoded_imgs = autoencoder.encoder(x_test).numpy()\n",
        "decoded_imgs = autoencoder.decoder(encoded_imgs).numpy()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "sfxr9NdBCP_x"
      },
      "outputs": [],
      "source": [
        "n = 10\n",
        "plt.figure(figsize=(20, 4))\n",
        "for i in range(n):\n",
        "\n",
        "    # display original + noise\n",
        "    ax = plt.subplot(2, n, i + 1)\n",
        "    plt.title(\"original + noise\")\n",
        "    plt.imshow(tf.squeeze(x_test_noisy[i]))\n",
        "    plt.gray()\n",
        "    ax.get_xaxis().set_visible(False)\n",
        "    ax.get_yaxis().set_visible(False)\n",
        "\n",
        "    # display reconstruction\n",
        "    bx = plt.subplot(2, n, i + n + 1)\n",
        "    plt.title(\"reconstructed\")\n",
        "    plt.imshow(tf.squeeze(decoded_imgs[i]))\n",
        "    plt.gray()\n",
        "    bx.get_xaxis().set_visible(False)\n",
        "    bx.get_yaxis().set_visible(False)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ErGrTnWHoUYl"
      },
      "source": [
        "## 세 번째 예: 이상 감지\n",
        "\n",
        "## 개요\n",
        "\n",
        "이 예에서는 [ECG5000 데이터세트](http://www.timeseriesclassification.com/description.php?Dataset=ECG5000)에서 이상을 감지하도록 autoencoder를 훈련합니다. 이 데이터세트에는 각각 140개의 데이터 포인트가 있는 5,000개의 [심전도](https://en.wikipedia.org/wiki/Electrocardiography)가 포함되어 있습니다. 단순화 된 버전의 데이터세트를 사용하고, 각 예제는 `0`(비정상 리듬에 해당) 또는 `1`(정상 리듬에 해당)으로 레이블이 지정됩니다. 여러분은 비정상 리듬을 식별하는 데 관심이 있습니다.\n",
        "\n",
        "참고: 레이블이 지정된 데이터세트를 사용하므로 지도 학습 문제라고 표현할 수 있습니다. 이 예의 목표는 사용 가능한 레이블이 없는 더 큰 데이터세트에 적용할 수 있는 이상 감지 개념을 설명하는 것입니다(예: 정상 리듬이 수천 개이고 비정상 리듬이 적은 경우).\n",
        "\n",
        "autoencoder를 사용하여 이상을 어떻게 감지하겠습니까? autoencoder는 재구성 오류를 최소화하도록 훈련되었습니다. autoencoder는 정상 리듬으로만 훈련한 다음 이 autoencoder를 사용하여 모든 데이터를 재구성합니다. 여기서 가설은 비정상 리듬의 경우에 재구성 오류가 더 클 것이라는 것입니다. 그런 다음 재구성 오류가 고정 임계값을 초과하는 경우, 리듬을 이상으로 분류합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "i5estNaur_Mh"
      },
      "source": [
        "### ECG 데이터 로드하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "y35nsXLPsDNX"
      },
      "source": [
        "사용할 데이터세트는 [timeseriesclassification.com](http://www.timeseriesclassification.com/description.php?Dataset=ECG5000)의 데이터세트를 기반으로 합니다.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KmKRDJWgsFYa"
      },
      "outputs": [],
      "source": [
        "# Download the dataset\n",
        "dataframe = pd.read_csv('http://storage.googleapis.com/download.tensorflow.org/data/ecg.csv', header=None)\n",
        "raw_data = dataframe.values\n",
        "dataframe.head()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "UmuCPVYKsKKx"
      },
      "outputs": [],
      "source": [
        "# The last element contains the labels\n",
        "labels = raw_data[:, -1]\n",
        "\n",
        "# The other data points are the electrocadriogram data\n",
        "data = raw_data[:, 0:-1]\n",
        "\n",
        "train_data, test_data, train_labels, test_labels = train_test_split(\n",
        "    data, labels, test_size=0.2, random_state=21\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "byK2vP7hsMbz"
      },
      "source": [
        "데이터를 `[0,1]`로 정규화합니다.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tgMZVWRKsPx6"
      },
      "outputs": [],
      "source": [
        "min_val = tf.reduce_min(train_data)\n",
        "max_val = tf.reduce_max(train_data)\n",
        "\n",
        "train_data = (train_data - min_val) / (max_val - min_val)\n",
        "test_data = (test_data - min_val) / (max_val - min_val)\n",
        "\n",
        "train_data = tf.cast(train_data, tf.float32)\n",
        "test_data = tf.cast(test_data, tf.float32)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BdSYr2IPsTiz"
      },
      "source": [
        "이 데이터세트에서 `1`로 레이블이 지정된 정상 리듬만 사용하여 autoencoder를 훈련합니다. 정상 리듬과 비정상 리듬을 분리합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "VvK4NRe8sVhE"
      },
      "outputs": [],
      "source": [
        "train_labels = train_labels.astype(bool)\n",
        "test_labels = test_labels.astype(bool)\n",
        "\n",
        "normal_train_data = train_data[train_labels]\n",
        "normal_test_data = test_data[test_labels]\n",
        "\n",
        "anomalous_train_data = train_data[~train_labels]\n",
        "anomalous_test_data = test_data[~test_labels]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wVcTBDo-CqFS"
      },
      "source": [
        "정상적인 ECG를 플롯합니다. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZTlMIrpmseYe"
      },
      "outputs": [],
      "source": [
        "plt.grid()\n",
        "plt.plot(np.arange(140), normal_train_data[0])\n",
        "plt.title(\"A Normal ECG\")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QpI9by2ZA0NN"
      },
      "source": [
        "비정상적인 ECG를 플롯합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zrpXREF2siBr"
      },
      "outputs": [],
      "source": [
        "plt.grid()\n",
        "plt.plot(np.arange(140), anomalous_train_data[0])\n",
        "plt.title(\"An Anomalous ECG\")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0DS6QKZJslZz"
      },
      "source": [
        "### 모델 빌드하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bf6owZQDsp9y"
      },
      "outputs": [],
      "source": [
        "class AnomalyDetector(Model):\n",
        "  def __init__(self):\n",
        "    super(AnomalyDetector, self).__init__()\n",
        "    self.encoder = tf.keras.Sequential([\n",
        "      layers.Dense(32, activation=\"relu\"),\n",
        "      layers.Dense(16, activation=\"relu\"),\n",
        "      layers.Dense(8, activation=\"relu\")])\n",
        "    \n",
        "    self.decoder = tf.keras.Sequential([\n",
        "      layers.Dense(16, activation=\"relu\"),\n",
        "      layers.Dense(32, activation=\"relu\"),\n",
        "      layers.Dense(140, activation=\"sigmoid\")])\n",
        "    \n",
        "  def call(self, x):\n",
        "    encoded = self.encoder(x)\n",
        "    decoded = self.decoder(encoded)\n",
        "    return decoded\n",
        "\n",
        "autoencoder = AnomalyDetector()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gwRpBBbg463S"
      },
      "outputs": [],
      "source": [
        "autoencoder.compile(optimizer='adam', loss='mae')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zuTy60STBEy4"
      },
      "source": [
        "autoencoder는 일반 ECG만 사용하여 훈련되지만, 전체 테스트세트를 사용하여 평가됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "V6NFSs-jsty2"
      },
      "outputs": [],
      "source": [
        "history = autoencoder.fit(normal_train_data, normal_train_data, \n",
        "          epochs=20, \n",
        "          batch_size=512,\n",
        "          validation_data=(test_data, test_data),\n",
        "          shuffle=True)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OEexphFwwTQS"
      },
      "outputs": [],
      "source": [
        "plt.plot(history.history[\"loss\"], label=\"Training Loss\")\n",
        "plt.plot(history.history[\"val_loss\"], label=\"Validation Loss\")\n",
        "plt.legend()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ceI5lKv1BT-A"
      },
      "source": [
        "재구성 오류가 정상 훈련 예제에서 하나의 표준 편차보다 큰 경우, ECG를 비정상으로 분류합니다. 먼저, 훈련 세트의 정상 ECG, autoencoder에 의해 인코딩 및 디코딩된 후의 재구성, 재구성 오류를 플롯해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "hmsk4DuktxJ2"
      },
      "outputs": [],
      "source": [
        "encoded_imgs = autoencoder.encoder(normal_test_data).numpy()\n",
        "decoded_imgs = autoencoder.decoder(encoded_imgs).numpy()\n",
        "\n",
        "plt.plot(normal_test_data[0], 'b')\n",
        "plt.plot(decoded_imgs[0], 'r')\n",
        "plt.fill_between(np.arange(140), decoded_imgs[0], normal_test_data[0], color='lightcoral')\n",
        "plt.legend(labels=[\"Input\", \"Reconstruction\", \"Error\"])\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ocA_q9ufB_aF"
      },
      "source": [
        "이번에는 비정상적인 테스트 예제에서 비슷한 플롯을 만듭니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vNFTuPhLwTBn"
      },
      "outputs": [],
      "source": [
        "encoded_imgs = autoencoder.encoder(anomalous_test_data).numpy()\n",
        "decoded_imgs = autoencoder.decoder(encoded_imgs).numpy()\n",
        "\n",
        "plt.plot(anomalous_test_data[0], 'b')\n",
        "plt.plot(decoded_imgs[0], 'r')\n",
        "plt.fill_between(np.arange(140), decoded_imgs[0], anomalous_test_data[0], color='lightcoral')\n",
        "plt.legend(labels=[\"Input\", \"Reconstruction\", \"Error\"])\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ocimg3MBswdS"
      },
      "source": [
        "### 이상 감지하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Xnh8wmkDsypN"
      },
      "source": [
        "재구성 손실이 고정 임계값보다 큰지 여부를 계산하여 이상을 감지합니다. 이 튜토리얼에서는 훈련 세트에서 정상 예제에 대한 평균 오차를 계산한 다음, 재구성 오류가 훈련 세트의 표준 편차보다 큰 경우 향후 예제를 비정상적인 것으로 분류합니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TeuT8uTA5Y_w"
      },
      "source": [
        "훈련 세트에서 정상 ECG에 대한 재구성 오류를 플롯합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gwLuxrb-s0ss"
      },
      "outputs": [],
      "source": [
        "reconstructions = autoencoder.predict(normal_train_data)\n",
        "train_loss = tf.keras.losses.mae(reconstructions, normal_train_data)\n",
        "\n",
        "plt.hist(train_loss, bins=50)\n",
        "plt.xlabel(\"Train loss\")\n",
        "plt.ylabel(\"No of examples\")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mh-3ChEF5hog"
      },
      "source": [
        "평균보다 표준 편차가 높은 임계값을 선택합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "82hkl0Chs3P_"
      },
      "outputs": [],
      "source": [
        "threshold = np.mean(train_loss) + np.std(train_loss)\n",
        "print(\"Threshold: \", threshold)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uEGlA1Be50Nj"
      },
      "source": [
        "참고: 테스트 예제를 이상 항목으로 분류하는 임계값을 선택하는 데 사용할 수 있는 다른 전략이 있습니다. 올바른 접근 방식은 데이터세트에 따라 다릅니다. 이 튜토리얼의 끝에 있는 링크를 통해 더 많은 것을 배울 수 있습니다. "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zpLSDAeb51D_"
      },
      "source": [
        "테스트 세트에서 비정상적인 예제에 대한 재구성 오류를 조사하면 대부분 임계값보다 더 큰 재구성 오류가 있음을 알 수 있습니다. 임계값을 변경하여 분류자의 [정밀도](https://developers.google.com/machine-learning/glossary#precision)와 [재현율](https://developers.google.com/machine-learning/glossary#recall)을 조정할 수 있습니다. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "sKVwjQK955Wy"
      },
      "outputs": [],
      "source": [
        "reconstructions = autoencoder.predict(anomalous_test_data)\n",
        "test_loss = tf.keras.losses.mae(reconstructions, anomalous_test_data)\n",
        "\n",
        "plt.hist(test_loss, bins=50)\n",
        "plt.xlabel(\"Test loss\")\n",
        "plt.ylabel(\"No of examples\")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PFVk_XGE6AX2"
      },
      "source": [
        "재구성 오류가 임계값보다 큰 경우 ECG를 이상으로 분류합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mkgJZfhh6CHr"
      },
      "outputs": [],
      "source": [
        "def predict(model, data, threshold):\n",
        "  reconstructions = model(data)\n",
        "  loss = tf.keras.losses.mae(reconstructions, data)\n",
        "  return tf.math.less(loss, threshold)\n",
        "\n",
        "def print_stats(predictions, labels):\n",
        "  print(\"Accuracy = {}\".format(accuracy_score(labels, preds)))\n",
        "  print(\"Precision = {}\".format(precision_score(labels, preds)))\n",
        "  print(\"Recall = {}\".format(recall_score(labels, preds)))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "sOcfXfXq6FBd"
      },
      "outputs": [],
      "source": [
        "preds = predict(autoencoder, test_data, threshold)\n",
        "print_stats(preds, test_labels)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HrJRef8Ln945"
      },
      "source": [
        "## 다음 단계\n",
        "\n",
        "autoencoder를 사용한 이상 탐지에 대해 자세히 알아보려면 Victor Dibia가 TensorFlow.js로 빌드한 훌륭한 [대화형 예제](https://anomagram.fastforwardlabs.com/#/)를 확인하세요. 실제 사용 사례의 경우, TensorFlow를 사용하여 [Airbus가 ISS 원격 측정 데이터에서 이상을 감지](https://blog.tensorflow.org/2020/04/how-airbus-detects-anomalies-iss-telemetry-data-tfx.html)하는 방법을 알아볼 수 있습니다. 기본 사항에 대해 자세히 알아보려면 François Chollet의 [블로그 게시물](https://blog.keras.io/building-autoencoders-in-keras.html)을 읽어보세요. 자세한 내용은 Ian Goodfellow, Yoshua Bengio, Aaron Courville의 [딥 러닝](https://www.deeplearningbook.org/)에서 14장을 확인하세요.\n"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "autoencoder.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
